#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import enum
import bnetd
import asyncio
import logging
from gi.repository import GLib
from bnet_util import Util


class WrtObConnManager:

    def __init__(self, param):
        self._param = param
        self._data = {}

        for p in self._getMyPlugins():
            p.set_context(_Context(self, p))

        asyncio.create_task(self._pluginNotifyInit())
        Util.asyncioRunUntilNoTaskLeft()

        # FIXME
        asyncio.create_task(self._pluginNotifyEgressChanged([
            bnetd.EgressIpHost("0.0.0.0/0"),
            bnetd.EgressIpHost("::/0"),
            bnetd.EgressDomainHost("."),
        ]))
        Util.asyncioRunUntilNoTaskLeft()

    def dispose(self):
        for v in self._data.values():
            cw = v[0]
            if cw.get_state() in [WrtObConnState.ACTIVATING, WrtObConnState.ACTIVATED]:
                cw.deactivate()
        # wait for deactivate complete

        asyncio.create_task(self._pluginNotifyEvent(bnetd.EventType.DISPOSE))
        Util.asyncioRunUntilNoTaskLeft()

    def get_connections(self):
        return [v[0] for v in self._data.values()]

    async def _cbAddObConn(self, plugin, uuid):
        # connection already exist
        # this can happen when the connection is previously removed when the connection is not in idle state
        if uuid in self._data:
            cw = self._data[uuid][0]
            assert cw._bRemove
            cw._bRemove = False
            return

        # add connection
        self._data[uuid] = (WrtLocalObConn(self, plugin.on_create_obconn(uuid, data)), None)

    async def _cbRemoveObConn(self, plugin, uuid):
        # connection is not in idle state, record remove flag only, removal would be done when the connection deactivates
        cw = self._data[uuid][0]
        if cw.get_state() != WrtObConnState.IDLE:
            assert not self._bRemove
            cw._bRemove = True
            return

        # remove connection
        del self._data[uuid]

    async def _pluginNotifyInit(self):
        for p in self._getMyPlugins():
            asyncio.create_task(p.on_init())

    async def _pluginNotifyEgressChanged(self):
        for p in self._getMyPlugins():
            asyncio.create_task(p.on_init())

    def _getMyPlugins(self):
        return self._param.pluginHub.getPluginListByType(bnetd.PluginType.OBCONN)


class WrtObConnState(enum.Enum):
    IDLE = "idle"
    ACTIVATING = "activating"
    ACTIVATED = "activated"
    DEACTIVATING = "deactivating"


class WrtLocalObConn(bnetd.ObConnBase.Callback):

    def __init__(self, parent, obconn):
        self._parent = parent
        self._bRemove = False               # whether this connection is to-be-removed

        self._conn = obconn
        self._conn.set_callback(self)

        self._bEnabled = self._conn.is_auto_connect()
        if self._bEnabled:
            self._state = WrtObConnState.IDLE
        else:
            self._state = None
        self._bRecheck = False
        self._downReason = None

    def is_enabled(self):
        return self._bEnabled

    def set_enabled(self, value):
        value = bool(value)
        assert value != self._bEnabled
        if value:
            self._state = WrtObConnState.IDLE
        else:
            self._state = None

    def get_state(self):
        assert self._bEnabled
        return self._state

    def get_inactive_reason(self):
        assert self._bEnabled
        assert self._state == WrtObConnState.IDLE
        return self._inactiveReason

    def is_rechecking(self):
        return self._bRecheck

    def activate(self, stage_id, stage_data):
        assert self._state == WrtObConnState.IDLE

    def deactivate(self, stage_id, stage_data):
        if self._state == WrtObConnState.ACTIVATING:
            # abort operation
            pass
        elif self._state == WrtObConnState.ACTIVATED:
            self._rechecking = False
        else:
            assert False

    def on_obconn_activated(self, connection_livedata):
        assert self._state == WrtObConnState.ACTIVATING

        self._state = WrtObConnState.ACTIVATED
        assert self._downReason is None

    def on_obconn_deactivated(self, reason):
        assert self._state in [WrtObConnState.ACTIVATING, WrtObConnState.DEACTIVATING]

        # commit removal if needed
        if self._bRemove:
            del self._parent._connDict[self._conn.get_uuid()]
            return

        self._state = WrtObConnState.IDLE
        self._downReason = reason
        self._rechecking = False

    def on_obconn_rechecking(self):
        assert self._state == WrtObConnState.ACTIVATED
        self._bRecheck = True

    def on_obconn_recheck_completed(self):
        assert self._state == WrtObConnState.ACTIVATED
        self._bRecheck = False


class WrtGatewayObConn(bnetd.ObConnBase.Callback):
    pass


class _Context(bnetd.ObConnPluginBase.Context):

    def __init__(self, parent, plugin):
        self._parent = parent
        self._plugin = plugin

    @property
    def LIB_OBCONN_DIR(self):
        return self._parent._param.LIB_OBCONN_DIR

    @property
    def CACHE_OBCONN_DIR(self):
        return self._parent._param.CACHE_OBCONN_DIR

    @property
    def CFG_OBCONN_DIR(self):
        return self._parent._param.CFG_OBCONN_DIR

    @property
    def settings(self):
        return self._parent._param.settings

    def is_in_gateway(self):
        return self._parent._param.isInGateway()

    async def on_obconn_available(self, uuid):
        self._parent._cbAddObConn(self._plugin, uuid)

    async def on_obconn_unavailable(self, uuid):
        self._parent._cbRemoveObConn(self._plugin, uuid)
